package com.doctcloud.common.core.utils.uuid;


/**
 * @ClassName 雪花算法生成器服务类
 * @Author doctcloud samula
 * @Descripe 用于生成分布式环境下的唯一ID，基于时间戳、业务类型、机器ID和序列号组合生成
 *           结构：[时间戳差值][业务类型][机器ID][序列号]（通过位运算拼接）
 * @Data 2025-10-07 17:14
 **/
public class SnowFlakService {
    // 系统上线基准时间戳（毫秒级），用于计算相对时间差（减少ID长度）
    // 此处值为2020-12-18 00:00:00的时间戳，可根据实际上线时间调整
    private final long startTime = 1608220800000L;
    // 业务类型标识（用于区分不同业务场景的ID）
    private long businessType;
    // 机器ID（用于区分不同服务器节点，避免分布式环境下ID冲突）
    private long workId;
    // 序列号（同一毫秒内生成多个ID时，通过序列号区分）
    private long serialNum = 1L;
    // region 位运算相关配置（定义各部分占用的比特位数及位移量）
    // 序列号占用的比特位数（决定同一毫秒内最大生成的ID数量：2^10=1024个）
    private final long serialNumBits = 10L;
    // 机器ID占用的比特位数（支持最大机器数量：2^7=128台）
    private final long workBits = 7L;
    // 业务类型占用的比特位数（支持最大业务类型数量：2^5=32种）
    private final long businessTypeBits = 5L;
    // 机器ID的位移量（序列号部分左移后，机器ID从该位置开始拼接）
    private final long workShift = serialNumBits;
    // 业务类型的位移量（机器ID左移后，业务类型从该位置开始拼接）
    private final long businessTypeShift = workShift + workBits;
    // 时间戳的位移量（业务类型左移后，时间戳从该位置开始拼接）
    private final long timestampShift = businessTypeShift + businessTypeBits;
    // 上次生成ID的时间戳（用于判断是否在同一毫秒内）
    private long lastTimeStamp = 0L;
    // 序列号的最大值（通过位运算计算：2^10 - 1 = 1023）
    // 用于限制序列号不超过当前比特位能表示的范围
    private final long serialNumMax = ~(-1L << serialNumBits);
    /**
     * 构造方法：初始化业务类型和机器ID
     * @param businessType 业务类型标识（需小于2^5=32）
     * @param workId 机器ID（需小于2^7=128）
     */
    public SnowFlakService(long businessType, long workId){
        this.businessType = businessType;
        this.workId = workId;
    }
    /**
     * 生成唯一ID（线程安全，通过synchronized保证并发安全）
     * @return 生成的雪花算法唯一ID
     */
    public synchronized long getId(){
        // 获取当前时间戳（毫秒级）
        long timeStamp = System.currentTimeMillis();
        // 若当前时间戳与上次生成ID的时间戳相同，说明在同一毫秒内
        if (lastTimeStamp == timeStamp){
            // 序列号自增，并与最大值做与运算（确保不超过范围）
            serialNum = (serialNum + 1) & serialNumMax;
            // 若序列号溢出（等于0，说明超过最大数量），则等待下一毫秒
            if (serialNum == 0){
                timeStamp = waiteNextMillis(timeStamp);
            }
        } else {
            // 不同毫秒，重置序列号（此处用时间戳与1的与运算获取0或1，作为初始序列号）
            serialNum = timeStamp & 1;
        }
        // 更新上次生成ID的时间戳
        lastTimeStamp = timeStamp;
        // 拼接各部分生成最终ID：
        // 1. 时间戳差值（当前时间戳 - 基准时间）左移到最高位
        // 2. 业务类型左移到对应位置
        // 3. 机器ID左移到对应位置
        // 4. 拼接序列号（最低位）
        return ((timeStamp - startTime) << timestampShift)
                |(businessType<<businessTypeShift)
                |(workId<<workShift)
                |serialNum;
    }
    /**
     * 等待到下一毫秒（当同一毫秒内序列号用完时调用）
     * @param timeStamp 当前毫秒时间戳
     * @return 下一毫秒的时间戳
     */
    private long waiteNextMillis(long timeStamp){
        long nowTimeStamp = System.currentTimeMillis();
        // 循环等待，直到获取到大于当前毫秒的时间戳
        while (timeStamp>=nowTimeStamp){
            nowTimeStamp = System.currentTimeMillis();
        }
        return nowTimeStamp;
    }
}
